package microcredit.model.bo;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

import microcredit.model.Member;
import microcredit.model.MemberGroup;
import microcredit.model.MemberStatusHistory;
import microcredit.model.MemberToGroup;
import microcredit.model.MemberToLoan;
import microcredit.model.TrainingType;
import microcredit.model.ValidationException;
import microcredit.model.domain.MemberInactiveReason;
import microcredit.model.domain.Status;
import microcredit.model.util.ModelUtils;

public class BOMember extends BOBase {

	public BOMember() {
	}
	
	public List<Member> readMembersNotByTrainingType(Long trainingID) {
		try {
			open();
			TypedQuery<Member> q = manager.createQuery("SELECT m FROM Member m left join fetch m.group " +
					" WHERE NOT EXISTS " +
					"  (SELECT t FROM MemberToTrainingType t " +
					"    WHERE t.memberID=m.id " +
					"      AND t.trainingTypeID=:tID) " +
					" ", Member.class);
			q.setParameter("tID", trainingID);
			List<Member> member = q.getResultList();
			commit();
			return member;
		} finally {
			close();
		}
	}
	
	public List<Member> readMembers() {
		try {
			open();
			TypedQuery<Member> q = manager.createQuery("SELECT m FROM Member m left join fetch m.group", Member.class);
			List<Member> member = q.getResultList();
			commit();
			return member;
		} finally {
			close();
		}
	}
	
	public List<Member> readActiveMembers() {
		try {
			open();
			TypedQuery<Member> q = manager.createQuery("SELECT m FROM Member m left join fetch m.group WHERE m.status=:active", Member.class);
			q.setParameter("active", Status.A);
			List<Member> member = q.getResultList();
			commit();
			return member;
		} finally {
			close();
		}
	}

	public List<Member> readMembersByTrainingType(Long trainingID) {
		try {
			open();
			TypedQuery<Member> q = manager.createQuery("SELECT DISTINCT(m) FROM Member m left join fetch m.group left outer join fetch m.memberToTrainingTypes, MemberToTrainingType t " +
					" where t.memberID=m.id AND t.trainingTypeID =:tID ", Member.class);
			q.setParameter("tID", trainingID);
			List<Member> member = q.getResultList();
			commit();
			return member;
		} finally {
			close();
		}
	}

	public List<Member> readMembersByGroup(Long groupID, boolean exclude, boolean onlyActive) {
		try {
			open();
			StringBuilder sql = new StringBuilder();
			sql.append(" SELECT m ");
			sql.append(" FROM Member m ");
			if (exclude) {
				sql.append(" WHERE m.groupID <> :groupID or m.groupID is null");
			} else {
				sql.append(" WHERE m.group.id=:groupID");
			}
			if (onlyActive) {
				sql.append(" AND m.status=:active");
			}
			
			TypedQuery<Member> q = manager.createQuery(sql.toString(), Member.class);
			q.setParameter("groupID", groupID);
			if (onlyActive) {
				q.setParameter("active", Status.A);
			}
			
			List<Member> member = q.getResultList();
			commit();
			return member;
		} finally {
			close();
		}
	}
	
	
	public MemberGroup getMemberGroup(long id) {
		MemberGroup m = null;
		try {
			open();
			
			m = manager.find(MemberGroup.class, id);
			
			commit();
		} finally {
			close();
		}
		return m;
	}
	
	public Member getMember(long id) {
		Member m = null;
		try {
			open();
			String query = "SELECT m FROM Member m " +
					" LEFT JOIN FETCH m.group " +
					" LEFT OUTER JOIN FETCH m.memberToLoans " +
					" LEFT OUTER JOIN FETCH m.memberToTrainingTypes " +
					" WHERE m.id=:id";
			Query q = manager.createQuery(query);
			q.setParameter("id", id);
			m = (Member)q.getResultList().get(0);
			m.getMemberToLoans();
			m.getMemberToTrainingTypes();
			m.getMemberToGroups();
			m.getMemberStatusHistories();
			commit();
		} finally {
			close();
		}
		return m;
	}

	public Member saveMember(Member m) throws ValidationException {
		try {
			open();
			
			validateNewMember(m);
			validateMemberStatusHistory(m);
			assignMemberToGroup(m);
			
			// at this point we guarantee save
			boolean isNew = m.getId() == null;
			
			if (isNew) {
				m = manager.merge(m);
			}
			
			List<MemberStatusHistory> histories = m.getMemberStatusHistories();
			for (MemberStatusHistory history : histories) {
				if (history.getSequenceID() == null) {
					history.setSequenceID(histories.indexOf(history)+0L);
				}
				history = manager.merge(history);
			}
			
			if (!isNew) {
				m = manager.merge(m);
			}
			
			commit();
		} finally {
			close();
		}
		return m;
	}
	
	private void validateMemberStatusHistory(Member m) {
		if (m.getStatus() == null) {
			m.setStatus(Status.A);
		}
		
		MemberStatusHistory history = m.getLastStatusHistory();
		if (history == null && m.getStatus() == Status.A) {
			history = new MemberStatusHistory();
			history.setStatus(Status.A);
			history.setMember(m);
			if (m.getMemberStatusHistories()== null){
				m.setMemberStatusHistories(new ArrayList<MemberStatusHistory>());
			}
			m.getMemberStatusHistories().add(history);
		} else if (history == null || !history.getStatus().equals(m.getStatus())) {
			throw new ValidationException("No member status history defined.");
		} else if (history.getStatus().equals(Status.I)) {
			if ( history.getReason() == null) {
				throw new ValidationException("No inactive reason defined.", Member.INACTIVE_REASON);
			} else if (history.getReason() == MemberInactiveReason.O
					&& (history.getReasonText() == null || history.getReasonText().isEmpty())) {
				throw new ValidationException("If inactive reason other is defined a reason text must be entered.", Member.INACTIVE_REASON_TEXT);
			}
			
			// validate if there are still open loans
			List<MemberToLoan> loans = m.getMemberToLoans();
			List<String> openLoans = new ArrayList<String>();
			if (loans != null) {
				for (MemberToLoan memberToLoan : loans) {
					if (!memberToLoan.isFinalized()) {
						openLoans.add(MemberToLoan.formatID(memberToLoan));
					}
				}
			}
			if (openLoans.size() > 0) {
				throw new ValidationException("Cannot deactivate member if there are still open loans "+openLoans+"");
			}
			
			// validate if the member has a membership in any group
			MemberGroup group = m.getGroup();
			if (group != null) {
				if (ModelUtils.equals(group.getChairmanMember(), m)) {
					throw new ValidationException("Cannot deactivate member if the member is chairman in "+group);
				} else if (ModelUtils.equals(group.getSecretaryMember(), m)) {
					throw new ValidationException("Cannot deactivate member if the member is secretary in "+group);
				} else if (ModelUtils.equals(group.getTresurerMember(), m)) {
					throw new ValidationException("Cannot deactivate member if the member is tresuerer in "+group);
				}
			}
		}
	}
	
	private void assignMemberToGroup(Member m) {
		List<MemberToGroup> memberToGroups = m.getMemberToGroups();
		if (memberToGroups != null && memberToGroups.size() > 0) {
			MemberToGroup oldGroup = memberToGroups.get(memberToGroups.size()-1);
			
			Long oldGroupID;
			if (oldGroup.getMemberGroupID() != null) {
				oldGroupID = oldGroup.getMemberGroupID();
			} else if (oldGroup.getMemberGroup() != null) {
				oldGroupID = oldGroup.getMemberGroup().getId();
			} else {
				oldGroupID = null;
			}
			
			if (oldGroup.getMemberGroup() == null && m.getGroup() == null) {
				return;
			} else if (m.getGroup() != null && ModelUtils.equals(oldGroupID, m.getGroup().getId())) {
				// no group change
				return;
			}
			// deactivate old group
			MemberGroup oldGroupEntity = oldGroup.getMemberGroup();
			if (oldGroupEntity != null) {
				// commented automatic remove. Throw an error instead.
//				boolean changed = false;
				if (equalsMember(oldGroupEntity.getChairmanMember(), m)) {
					throw new ValidationException("Could not change member group. The member is "+oldGroupEntity+"'s chairman.");
//					oldGroupEntity.setChairmanMember(null);
//					changed = true;
				} 
				if (equalsMember(oldGroupEntity.getSecretaryMember(), m)) {
					throw new ValidationException("Could not change member group. The member is "+oldGroupEntity+"'s secretary.");
//					oldGroupEntity.setSecretaryMember(null);
//					changed = true;
				}
				if (equalsMember(oldGroupEntity.getTresurerMember(), m)) {
					throw new ValidationException("Could not change member group. The member is "+oldGroupEntity+"'s tresurer.");
//					oldGroupEntity.setTresurerMember(null);
//					changed = true;
				}
//				if (changed){
//					manager.merge(oldGroupEntity);
//				}
			}
			oldGroup.setStatus(Status.I);
			oldGroup.setUntil(ModelUtils.createTimestamp());
			manager.merge(oldGroup);
		} 
		if (memberToGroups == null) {
			memberToGroups = new ArrayList<MemberToGroup>();
		}
		
		MemberToGroup memberToGroup = new MemberToGroup();
		memberToGroup.setMember(m);
		memberToGroup.setSequenceID(memberToGroups.size()+0L);
		memberToGroup.setStatus(Status.A);
		memberToGroup.setMemberGroup(m.getGroup());
		memberToGroup.setSince(ModelUtils.createTimestamp());
		memberToGroups.add(memberToGroup);
		
		m.setMemberToGroups(memberToGroups);
	}
	
	private boolean equalsMember(Member m1, Member m2) {
		if (m1 == null || m2 == null) {
			return false;
		}
		return ModelUtils.equals(m1.getId(), m2.getId());
	}
	
	private void validateNewMember(Member m) throws ValidationException {
		m.validate();
	}
	
	public void deleteMember(Member m) {
		try {
			open();
			
			if (m.getMemberToLoans().size() == 0
					&& m.getMemberToTrainingTypes().size() == 0
					&& m.getGroup() == null
					) {
				m = manager.merge(m);
				
				for (MemberStatusHistory history : m.getMemberStatusHistories()) {
					history = manager.merge(history);
					manager.remove(history);
				}
				
				for (MemberToGroup group : m.getMemberToGroups()) {
					group = manager.merge(group);
					manager.remove(group);
				}
				
				manager.remove(m);		
				
			} else {
				m = manager.merge(m);
				manager.remove(m);
			}
			commit();
		} catch (PersistenceException pE) {
			if (handleIntegrity(pE)) {
				throw new ValidationException("Cannot delete member if loans or trainings are assigned. (Deactivate it)", pE);
			}
		} finally {
			close();
		}
	}
	
	
	@SuppressWarnings("unchecked")
	public List<TrainingType> readTrainingTypes() {
		try {
			open();
			Query q = manager.createQuery("SELECT t FROM TrainingType t");
			List<TrainingType> trainingTypes = (List<TrainingType>)q.getResultList();
			commit();
			return trainingTypes;
		} finally {
			close();
		}
	}
	
	
	public static BOMember create() {
		return new BOMember();
	}

}
